package ca.mcgill.model.digraph.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ca.mcgill.model.digraph.Digraph;
import ca.mcgill.model.digraph.DigraphEdge;
import ca.mcgill.model.digraph.TransactionEdge;
import ca.mcgill.model.digraph.TransactionDigraph;
import ca.mcgill.model.functional.FiniteStateMachine;
import ca.mcgill.model.functional.Transition;
import ca.mcgill.model.implementation.ImplementationModel;
import ca.mcgill.model.implementation.Task;
import ca.mcgill.model.implementation.Transaction;

/**
 * This class contains methods which generate the offset edges in the digraph.
 * The offset edges are used for the optimized way of computing rbf/dbf
 * functions.
 * 
 * @author EW228618
 * 
 */
public class TransactionDigraphUtils {

	PreprocessingUtils pUtils;

	public TransactionDigraphUtils() {

	}

	public Map<Transaction, TransactionDigraph> createTransactionDigraphs(
			ImplementationModel implModel, Map<Task, Digraph> taskToDigraph) {

		Map<Transaction, TransactionDigraph> result = new HashMap<Transaction, TransactionDigraph>();

		pUtils = new PreprocessingUtils(implModel.getImplementedNetwork());

		for (Transaction transaction : implModel.getTransactions()) {

			result.put(
					transaction,
					createTransactionDigraph(transaction, taskToDigraph, pUtils));

		}

		return result;

	}

	public TransactionDigraph createTransactionDigraph(Transaction transaction,
			Map<Task, Digraph> taskToDigraph, PreprocessingUtils pUtils) {

		TransactionDigraph result = null;

		List<Transition> vertices = new ArrayList<Transition>();
		List<DigraphEdge> edges = new ArrayList<DigraphEdge>();

		Map<Task, List<TransactionEdge>> taskToEdges = new HashMap<Task, List<TransactionEdge>>();

		for (int i = 0; i < transaction.getTasksInTransaction().size(); i++) {

			Task prevTask = transaction.getTasksInTransaction().get(i);

			Digraph prevDigraph = taskToDigraph.get(prevTask);

			vertices.addAll(prevDigraph.getVertices());
			edges.addAll(prevDigraph.getEdges());

			if (i == transaction.getTasksInTransaction().size() - 1) {
				break;
			}

			Task followingTask = transaction.getTasksInTransaction().get(i + 1);
			Digraph followingDigraph = taskToDigraph.get(followingTask);

			List<TransactionEdge> trnsEdges;

			if ((trnsEdges = taskToEdges.get(prevTask)) == null) {
				trnsEdges = new ArrayList<TransactionEdge>();
				taskToEdges.put(prevTask, trnsEdges);
			}

			for (Transition vertex : followingDigraph.getVertices()) {

				for (Transition source : pUtils.getEventsSrcTransitions().get(
						vertex.getTrigE())) {

					TransactionEdge trnsEdge = new TransactionEdge(source
							.getAction().getBCET(), source, vertex);

					trnsEdges.add(trnsEdge);

					edges.add(trnsEdge);

				}

			}

		}

		result = new TransactionDigraph(transaction);
		result.addVertices(vertices);
		result.addEdges(edges);
		result.setTaskToOutTrnsEdges(taskToEdges);

		return result;

	}

	/**
	 * This method is used to shorten the transaction digraph based on the
	 * priority of the source task of the transaction for which the
	 * schedulability condition is being checked.
	 * 
	 * @param trnsDig
	 *            transaction digraph that will be shortened
	 * @param taskToDigraph
	 *            mapping between the tasks and their digraphs
	 * @param priorityBound
	 *            priority of the source task of the transaction for which the
	 *            schedulability condition is being checked
	 * @return
	 */
	public TransactionDigraph trnsDigPreprocessing(TransactionDigraph trnsDig,
			Map<Task, Digraph> taskToDigraph, int priorityBound) {

		TransactionDigraph result = new TransactionDigraph(
				trnsDig.getTransaction());

		List<Transition> vertices = new ArrayList<Transition>(
				trnsDig.getVertices());
		result.addVertices(vertices);

		List<DigraphEdge> edges = new ArrayList<DigraphEdge>(trnsDig.getEdges());
		result.addEdges(edges);

		Map<Task, List<TransactionEdge>> taskToEdges = new HashMap<Task, List<TransactionEdge>>(
				trnsDig.getTaskToOutTrnsEdges());
		result.setTaskToOutTrnsEdges(taskToEdges);

		int taskPr;
		for (Task task : trnsDig.getTransaction().getTasksInTransaction()) {

			taskPr = task.getPriority();

			if (taskPr < priorityBound) {

				Digraph taskDig = taskToDigraph.get(task);

				// removal of the edges and vertices
				if (task != trnsDig
						.getTransaction()
						.getTasksInTransaction()
						.get(trnsDig.getTransaction().getTasksInTransaction()
								.size() - 1)) {

					result.getEdges().removeAll(
							result.getTaskToOutTrnsEdges().get(task));

				}

				result.getEdges().removeAll(taskDig.getEdges());

				result.getVertices().removeAll(taskDig.getVertices());

			} else {
				// knowing that the priorities order is ascending we can already
				// break the loop as there will be no other tasks which digraphs
				// will have to be removed from the transaction digraph
				break;
			}

		}

		return result;

	}
}
